import serial
import pilink
import socket
from datetime import datetime

class pilink_serial_parser:
    def __init__(self):
        self.port = None
        self.msg_cb = None
        self.parser = pilink.PilinkHandler(0x05)

    def set_port(self, port, port_timeout):
        if self.port and self.port.is_open:
            self.port.close()
        self.port = None
        try:
            self.port = serial.Serial(port, baudrate=115200, timeout=port_timeout)
            print(f"[PilinkParser]open port: {port}")
        except serial.SerialException as e:
            print(f"[PilinkParser]Serial Exception, check the serial please: {e}")
            self.port.close()
            self.port=None
        except PermissionError as e:
            print(f"[PilinkParser]Permission Error: {e}")
            self.port.close()
            self.port=None
        except Exception as e:
            print(f"[PilinkParser]Unexpected Error: {e}")
            self.port.close()
            self.port=None

    def close_port(self):
        if self.port and self.port.is_open:
            self.port.close()
            print(f"[PilinkParser]close port: {self.port.port}")
            self.port = None
    
    def set_msg_callback(self, cb):
        self.msg_cb = cb

    def update(self):
        if self.port and self.port.is_open:
            try:
                if self.port.in_waiting > 0:
                    data = self.port.read(self.port.in_waiting)
                    
                    for byte in data:
                        ret = self.parser.parse(byte)
                        if ret > 0:
                            if self.msg_cb :
                                self.msg_cb(self.parser.port, self.parser.rxbuf, self.parser.rx_msg_len)
                        elif ret < 0:
                            print(f"[PilinkParser]parsing error:{ret}")
                        
            except serial.SerialException as e:
                print(f"[PilinkParser]Serial Exception, check the serial please: {e}")
                self.port.close()
                self.port=None
            except PermissionError as e:
                print(f"[PilinkParser]Permission Error: {e}")
                self.port.close()
                self.port=None
            except Exception as e:
                print(f"[PilinkParser]Unexpected Error: {e}")
                self.port.close()
                self.port=None

    def send_camera_cmd(self, command, param1, param2, param3):
        cmd = pilink.PilinkCameraCmdStruct()
        cmd.command = command
        cmd.param1  = param1
        cmd.param2  = param2
        cmd.param3  = param3
        buf = pilink.pilink_camera_cmd_encode(cmd)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_CMD, buf, len(buf))

        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

    def send_date(self):
        date = pilink.PilinkCameraUTCTime()

        now = datetime.now()
        date.year = now.year
        date.mon  = now.month
        date.day  = now.day
        date.hour = now.hour
        date.min  = now.minute
        date.sec  = now.second

        buf = pilink.pilink_camera_UTC_time_encode(date)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_UTC_TIME, buf, len(buf))

        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

    def set_param(self, name, value):
        param = pilink.PilinkCameraParam()

        if len(name) > 30:
            print(f"[PilinkParser]param name ({name}) is too long")
        else:
            param.name_len = len(name)
            param.name[:param.name_len] = name[:param.name_len]
            param.value  = value
            param.cmd    = pilink.PILINK_CAMERA_PARAM_SET

            buf = pilink.pilink_camera_param_encode(param)

            package_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_PARAM, buf, len(buf))
            if self.port and self.port.is_open:
                self.port.write(self.parser.txbuf[:package_len])

    def send_params(self, params):
        buf = pilink.pilink_camera_params_encode(params)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_PARAMS, buf, len(buf))
        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])


    def send_sensor_info(self, sensor_type, value1, value2, value3, value4):
        info = pilink.PilinkCameraSensorInfo()

        info.type   = sensor_type
        info.value1 = value1
        info.value2 = value2
        info.value3 = value3
        info.value4 = value4

        buf = pilink.pilink_sensor_info_encode(info)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_SENSOR_INFO, buf, len(buf))
        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

    def send_terminal_data(self, cmd, channel, msg:bytearray):
        info = pilink.PilinkTerminalData()
        
        info.cmd   = cmd
        info.channel = channel
        info.msg_len = len(msg)
        if info.msg_len > 128:
            info.msg_len = 128
        info.msg[0:info.msg_len] = msg[0:info.msg_len]

        buf = pilink.pilink_terminal_data_encode(info)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_TERMINAL, buf, len(buf))
        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

    def send_boot_cmd(self, cmd, value1, value2, value3):
        cmd_msg = pilink.PilinkBootCmd()

        cmd_msg.cmd    = cmd
        cmd_msg.param0 = value1
        cmd_msg.param1 = value2
        cmd_msg.param2 = value3

        buf = pilink.pilink_boot_cmd_encode(cmd_msg)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_BOOT_CMD, buf, len(buf))
        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

    def send_boot_data(self, addr, data):
        dat_msg = pilink.PilinkBootData()

        dat_msg.addr = addr
        dat_msg.data = data

        buf = pilink.pilink_boot_data_encode(dat_msg)

        package_len = self.parser.msg_pack(pilink.PILINK_TYPE_BOOT_DATA, buf, len(buf))
        if self.port and self.port.is_open:
            self.port.write(self.parser.txbuf[:package_len])

class pilink_udp_parser:
    def __init__(self):
        self.port = None
        self.msg_cb = None
        self.parser = pilink.PilinkHandler(0x05)
        self.udp_sock = None
        self.udp_remote_addr = None

    def set_port(self, addr, local_port):
        try:
            if self.udp_sock is not None:
                self.udp_sock.close()
                self.udp_sock = None
            self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.udp_sock.bind((addr, local_port))
            self.udp_sock.setblocking(False)
            print(f"[PilinkParser]UDP bind on port {local_port}")
        except Exception as e:
            print(f"[PilinkParser]Failed to create UDP socket: {e}")
            self.udp_sock = None
    
    def set_remote(self, remote_addr, remote_port):
        self.udp_remote_addr = (remote_addr, remote_port)

    def close(self):
        if self.udp_sock:
            self.udp_sock.close()
            self.udp_sock = None
            print("[PilinkParser]UDP socket closed")

    def set_msg_callback(self, cb):
        self.msg_cb = cb

    def update(self):
        try:
            data, addr = self.udp_sock.recvfrom(2048)
            self.udp_remote_addr = addr
            self._parse_data(data)
        except (BlockingIOError, ConnectionResetError) as e:
            # 非阻塞没有数据 或 UDP连接被远程关闭，直接跳过
            pass


    def _parse_data(self, data: bytes):
        for byte in data:
            ret = self.parser.parse(byte)
            if ret > 0:
                if self.msg_cb:
                    self.msg_cb(self.parser.port, self.parser.rxbuf, self.parser.rx_msg_len)
            elif ret < 0:
                print(f"[PilinkParser]parsing error: {ret}")

    def _send_data(self, data: bytes):
        if self.udp_remote_addr is None:
            return
        self.udp_sock.sendto(data, self.udp_remote_addr)

    def send_camera_cmd(self, command, param1, param2, param3):
        cmd = pilink.PilinkCameraCmdStruct()
        cmd.command = command
        cmd.param1 = param1
        cmd.param2 = param2
        cmd.param3 = param3
        buf = pilink.pilink_camera_cmd_encode(cmd)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_CMD, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])

    def send_date(self):
        now = datetime.now()
        date = pilink.PilinkCameraUTCTime()
        date.year, date.mon, date.day = now.year, now.month, now.day
        date.hour, date.min, date.sec = now.hour, now.minute, now.second
        buf = pilink.pilink_camera_UTC_time_encode(date)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_UTC_TIME, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])

    def set_param(self, name, value):
        if len(name) > 30:
            print(f"[PilinkParser]param name too long: {name}")
            return

        param = pilink.PilinkCameraParam()
        param.name_len = len(name)
        param.name[:param.name_len] = name[:param.name_len]
        param.value = value
        param.cmd = pilink.PILINK_CAMERA_PARAM_SET
        buf = pilink.pilink_camera_param_encode(param)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_PARAM, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])

    def send_params(self, params):
        buf = pilink.pilink_camera_params_encode(params)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_PARAMS, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])

    def send_sensor_info(self, sensor_type, value1, value2, value3, value4):
        info = pilink.PilinkCameraSensorInfo()
        info.type = sensor_type
        info.value1 = value1
        info.value2 = value2
        info.value3 = value3
        info.value4 = value4
        buf = pilink.pilink_sensor_info_encode(info)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_CAM_SENSOR_INFO, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])

    def send_terminal_data(self, cmd, channel, msg: bytearray):
        info = pilink.PilinkTerminalData()
        info.cmd = cmd
        info.channel = channel
        info.msg_len = min(len(msg), 128)
        info.msg[0:info.msg_len] = msg[0:info.msg_len]
        buf = pilink.pilink_terminal_data_encode(info)
        pkg_len = self.parser.msg_pack(pilink.PILINK_TYPE_TERMINAL, buf, len(buf))
        self._send_data(self.parser.txbuf[:pkg_len])
